package com.jl.card.file;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.annotations.JSConstructor;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;

import com.jl.card.Card;
import com.jl.card.ErrorProcess;
import com.jl.card.ICard;
import com.jl.card.ICardProvider;
import com.jl.card.CardJSProxy;
import com.jl.card.SWException;
import com.jl.js.core.ByteArray;
import com.jl.util.HexUtil;

public class CardFile extends ScriptableObject {

	private static final long serialVersionUID = 2013217095027L;
	/** Java Script Class name */
	private final static String clazzName = "CardFile";
	/** the card file path */
	private CardFilePath path = null;
	/** the object of access the card file */
	private IFileAccess fileAccess = null;
	/** the card file infomation object */
	private IFileInfo info = null;
	/** card provider */
	private ICardProvider provider;
	/** comunication with the card */
	private Card card = null;
	/** chech the this object is call the select function or not */
	private boolean isSelect = false;
	/** record the file is exists or not */
	private boolean exist;

	/**
	 * only used for js environment
	 */
	public CardFile() {
	}

	@JSConstructor
	public CardFile(Object card, String filepath, boolean selectNow)
			throws SWException {
		Object jsCard = Context.jsToJava(card, ICard.class);
		if (jsCard instanceof Card) {
			this.card = (Card) jsCard;
		} else if (jsCard instanceof CardJSProxy) {
			this.card = ((CardJSProxy) jsCard).getInnerCard();
		}else{
			throw new RuntimeException("parameter one must be instance of Card");
		}
		path = new CardFilePath(filepath);
		provider = this.card.getProvider();
		fileAccess = provider.getFileAccess(path, this.card);
		if (selectNow) {
			select();
		}
	}

	public CardFile(Card card, String filepath, boolean isSelect)
			throws SWException {
		this(card, filepath);
		if (isSelect) {
			select();
		}
	}

	public CardFile(Card card, String filepath) {
		this.card = card;
		path = new CardFilePath(filepath);
		provider = this.card.getProvider();
		fileAccess = provider.getFileAccess(path, this.card);
	}

	@JSFunction
	public void select() throws SWException {
		isSelect = true;
		info = fileAccess.select();
		if (info == null) {
			exist = false;
		} else {
			exist = true;
		}
	}

	@JSFunction
	public byte[] readBinary(int from, int len) throws SWException {
		if (!isSelect) {
			select();
		}
		return fileAccess.readBinary(from, len);
	}

	@JSFunction
	public byte[] read(int from, int len) throws SWException {
		if (!isSelect) {
			select();
		}
		return fileAccess.read(from, len);
	}

	public void updateBinary(int from, byte[] data, int off, int len)
			throws SWException {
		if (!isSelect) {
			select();
		}
		fileAccess.updateBinary(from, data, off, len);
	}

	public void updateBinary(int from, byte[] data, int len) throws SWException {
		updateBinary(from, data, 0, len);
	}

	@JSFunction
	public void updateBinary(int from, String data) throws SWException {
		byte[] array = HexUtil.parseHexToByteArray(data);
		updateBinary(from, array, array.length);
	}

	@JSFunction
	public byte[] readRecord(int recNo) throws SWException {
		if (!isSelect) {
			select();
		}
		return fileAccess.readRecord(recNo);
	}

	@JSFunction
	public void updateRecord(int recNo, String data) throws SWException {
		updateRecord(recNo, HexUtil.parseHexToByteArray(data));
	}

	public void updateRecord(int recNo, byte[] data, int off)
			throws SWException {
		if (!isSelect) {
			select();
		}
		fileAccess.updateRecord(recNo, data, off);
	}

	public void updateRecord(int recNo, byte[] data) throws SWException {
		updateRecord(recNo, data, 0);
	}

	@JSFunction
	public byte[] all() throws SWException {
		if (!isSelect) {
			select();
		}
		if (info.isDirectory()) {
			return new byte[0];
		}
		if (info.isTransparent()) {
			return read(0, info.getLength());
		}
		byte[] all = new byte[info.getLength()];
		// record
		int index = 0;
		for (int i = 0; i < recordNum(); i++) {
			byte[] one = readRecord(i + 1);
			System.arraycopy(one, 0, all, index, one.length);
			index += one.length;
		}
		return all;
	}

	@JSFunction
	public String dump(boolean withASCII) throws SWException {
		byte[] all = all();
		if (withASCII) {
			return HexUtil.dump(all, 0, all.length);
		}
		if (info.isTransparent()) {
			return new ByteArray(all).format(16);
		} else if (!info.isDirectory()) {
			return new ByteArray(all).format(info.getRecordSize());
		} else {// is df or mf
			return "";
		}
	}

	@JSGetter
	public String info() {
		if (!exists()) {
			return "The File is Not Exists";
		}
		if (info == null) {
			return "Error, info is null, maybe the file is not selected now,"
					+ "Or the file is not exists";
		}
		return info.toString();
	}

	@JSGetter
	public boolean exists() {
		if (!isSelect) {
			try {
				select();
			} catch (SWException e) {
				exist = false;
			}
		}
		return exist;
	}

	@Override
	@JSGetter
	public int size() {
		if (info == null) {
			return -1;
		}
		return info.getLength();
	}

	@JSGetter
	public int recordSize() {
		if (info == null) {
			return -1;
		}
		if (info.isDirectory() || info.isTransparent()) {
			ErrorProcess.throwRuntimeException("File is not a record file");
		}
		return info.getRecordSize();
	}

	@JSGetter
	public int recordNum() {
		if (info == null) {
			return -1;
		}
		if (info.isDirectory() || info.isTransparent()) {
			ErrorProcess.throwRuntimeException("File is not a record file");
		}
		return info.getLength() / info.getRecordSize();
	}

	@Override
	public String getClassName() {
		return clazzName;
	}

	@Override
	@JSFunction
	public String toString() {
		return "[object CardFile]";
	}

	@JSGetter
	public String path() {
		return path.toString();
	}

	// convinient function
	@JSFunction
	public byte[] rb(int off, int len) throws SWException {
		return readBinary(off, len);
	}

	@JSFunction
	public void ub(int off, String data) throws SWException {
		updateBinary(off, data);
	}

	@JSFunction
	public byte[] rr(int num) throws SWException {
		return readRecord(num);
	}

	@JSFunction
	public void ur(int num, String data) throws SWException {
		updateRecord(num, data);
	}

	public IFileInfo getCardFileInfo() {
		return info;
	}

	public Card getCard() {
		return this.card;
	}

	public CardFilePath getCardFilePath() {
		return this.path;
	}

}
